// <copyright file="DungeonGenerator.PrivateMethods.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>Dungeon level generator.</summary>

// License:

// Product: RPGRoguelikeEngine .Net
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

using TenneySoftware.Engine;

namespace TenneySoftware.LevelGenerator.DungeonGenerator {
    /// <content>
    /// This file contains the Private methods for the generator.
    /// </content>
    public partial class Dungeon {
        /// <summary>
        /// Extends the given area by one all the way around.
        /// </summary>
        /// <param name="area">Area to extend.</param>
        /// <returns>A new rectangle that is larger than the given.</returns>
        private static Rectangle ExtendArea(Rectangle area) {
            Rectangle temp = area;
            temp.Inflate(1, 1);
            return temp;
        }

        /// <summary>
        /// Converts a point to a Rectangle with a width and height of 1.
        /// </summary>
        /// <param name="point">Point to convert.</param>
        /// <returns>A rectangle equivalent of the point.</returns>
        private static Rectangle ConvertPointToRectangle(Point point) {
            return new Rectangle(point, new Size(1, 1));
        }

        /// <summary>
        /// Calculates the total for the structure weights.
        /// </summary>
        /// <returns>Returns the total.</returns>
        private double TotalWeights() {
            double results = 0;
            if (this.structureWeights.Count != 0) {
                double tally = 0;
                foreach (KeyValuePair<StructureType, double> kvp in this.structureWeights) {
                    tally += kvp.Value;
                }

                results = tally;
            } else {
                throw new InvalidStructureException("Structure Weight list is empty.");
            }

            return results;
        }

        /// <summary>
        /// Method to generate the dungeon level.
        /// </summary>
        /// <returns>True if level was generated.</returns>
        private bool DungeonGenerate() {
            bool placed = false, complete = false;
            int failCount = 0;
            Heading nextConnector;
            bool first = true;

            // Loop until generation is complete.
            do {
                // Process the weights and place results into the 'results' variable.
                WeightedResults results = this.ProcessStructureWeights();

                // More local variable
                int placementFail = 0;
                int placementFailMax = 100;

                // Loop until a structure is placed.
                do {
                    // Find a location to connect a new structure to.
                    nextConnector = this.GenerateConnectorPoint();

                    // If the fail counter maxed out then leave loop.
                    if (placementFail >= placementFailMax) {
                        break;
                    }

                    // If a room was selected from the weights, then....
                    if (results.IsRoom) {
                        // .... Generate a room at the selected connector point.
                        placed = this.GenerateRoom(nextConnector, results);

                        // If not a room, then a hall it must be.....
                    } else {
                        // .... so generate a hall.
                        placed = this.GenerateHall(nextConnector, results);
                    }

                    // Placement must have failed....increase fail counter....
                    placementFail++;
                } while (!placed);

                // Check to see if we got here by failure or by placement.
                if (!placed) {
                    // If by failure, increase fail counter for mail loop.
                    failCount++;

                    // If failure maxed out, then flag this as complete. Could not place
                    // any more structures....Fail counter max is determined by density...
                    // A less dense level causes a smaller fail count and an earlier exit from
                    // the generator.
                    if (failCount >= this.failure) {
                        complete = true;
                    }
                }

                // If placement was made, AND this is not the first structure to be made...
                if (placed && !first) {
                    // Check to see if a door is going to be placed connecting the two structures.
                    if ((this.fromRoom && this.toRoom) || (!this.fromRoom && this.toRoom) || (this.fromRoom && !this.toRoom)) {
                        this.doorList.Add(nextConnector.Location);
                    } else {
                        // Or if the connection is between two hallways, then connect with a hall segement.
                        this.level.GetLevel(nextConnector.Location).TypeOfTile = TileType.Floor;
                        this.hallSegments.Add(nextConnector);
                    }
                }

                // If place, and this is the first structure placed...
                if (placed && first) {
                    // Turn off the first flag.
                    first = false;
                }
            } while (!complete);

            // If settings calls for no dead ends, then run the dead end remover.
            if (this.RemoveDeadEnds) {
                this.RemoveDeadEndsMethod();
            }

            // Always remove dead doors....Dead doors mainly come from dead end removal
            // that attached to a door. The door would be useless with the dead end hall
            // way removed.
            this.RemoveDeadDoors();

            // Place the doors on the level from the door list.
            foreach (Point point in this.doorList) {
                this.level.GetLevel(point).TypeOfTile = TileType.Door;
            }

            // Search all the rooms placed, and look for ones marked as cave.
            foreach (RoomData room in this.roomList) {
                if (room.Type.ToString().Contains("Cave")) {
                    // Create a Cavern Generator object using the cavernous predefined settings.
                    // Use the room dimensions at the size.
                    bool done = false;
                    do {
                        this.cave = (ILevelGenerator)Activator.CreateInstance(
                                        this.cavernAssem.GetType("TenneySoftware.LevelGenerator.CavernGenerator.Cavern"),
                                        new object[] { 2, room.Area.Size });
                        this.cave.SetUniqueProperties(new object[][] {
                            new object[] { 3, 0 },
                            new object[] { 6, 0 },
                            new object[] { 4, 1 }
                        });

                        // Set the seed from this generator to the cavern generator.
                        this.cave.RandomSeed = this.seed;

                        // Extend the room area by one all the way around to accomodate the placement
                        // of exits.
                        Rectangle extend = ExtendArea(room.Area);

                        // For each connector to the cavern, place a Passage out seed into the generator.
                        foreach (Point door in this.doorList) {
                            if (extend.Contains(door)) {
                                Point location = door;
                                location.X -= room.Area.Location.X;
                                location.Y -= room.Area.Location.Y;
                                this.cave.UseUniqueMethods(new object[][] { new object[] { 0, location } });
                            }
                        }

                        // Call the generator...
                        done = this.cave.Generate();
                    } while (!done);

                    // Get the level generated...
//                    Level cavern = this.cave.Level;

                    // And place them in the proper place within the level.
                    for (int y = 0; y < this.cave.Level.Size.Height; y++) {
                        for (int x = 0; x < this.cave.Level.Size.Width; x++) {
                            Point location = new Point(x, y);
                            TileData tile = this.cave.Level.GetLevel(location);
                            location.Offset(room.Area.Location);
                            this.level.SetLevel(location, tile);
                        }
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Sets the name of the settings.
        /// </summary>
        /// <param name="name">Name to set the settings to.</param>
        private void SetName(string name) {
            this.settingName = name;
        }

        /// <summary>
        /// Removes all dead ends from the level.
        /// </summary>
        private void RemoveDeadEndsMethod() {
            bool complete = false;
            HashSet<Heading> removal;
            int areaCount = 0;
            int iter = 0;
            do {
                iter++;
                removal = new HashSet<Heading>();
                foreach (Heading segment in this.hallSegments) {
                    areaCount = this.CountNeighbors(segment.Location);
                    if (areaCount <= 1) {
                        removal.Add(segment);
                    }
                }

                if (removal.Count == 0) {
                    complete = true;
                }

                foreach (Heading segment in removal) {
                    this.hallSegments.Remove(segment);
                    this.level.GetLevel(segment.Location).TypeOfTile = TileType.Wall;
                }
            } while (!complete);
        }

        /// <summary>
        /// Removes all dead doors (Doors that lead nowhere.).
        /// </summary>
        private void RemoveDeadDoors() {
            List<Point> removal;
            int areaCount = 0;
            removal = new List<Point>();
            foreach (Point door in this.doorList) {
                areaCount = this.CountNeighbors(door);
                if (areaCount <= 1) {
                    removal.Add(door);
                }
            }

            foreach (Point door in removal) {
                this.doorList.Remove(door);
            }
        }

        /// <summary>
        /// Generates a new point of connection. If there is nothing to build from, it will generate a random point and direction,
        /// else it picks a structure to connect from.
        /// </summary>
        /// <returns>The connecting point and direction.</returns>
        private Heading GenerateConnectorPoint() {
            this.fromRoom = false;
            Heading temp = new Heading(Point.Empty, Direction.Void);
            int pick = this.rand.Next(1, 101);

            // If random number is less than preferedChance value and greater than 0 and if there is values in the preferedConnectors
            // list, then pick one of them at random, and return that as the new connector point and direction.
            if (pick <= this.UsePreferredConnectors && pick > 0 && this.preferredConnectors.Count > 0) {
                pick = this.rand.Next(this.preferredConnectors.Count);
                temp = this.preferredConnectors[pick];
                this.preferredConnectors.RemoveAt(pick);
                this.fromRoom = false;
            } else {
                // Otherwise, pick a connecting point and direction at random.
                // Select either a room to build from, or a hall to build from.
                pick = this.rand.Next(1, 101);

                // If random number is equal to or below the room bias, and there are rooms available, then build from a room,
                // or if there are no halls to build from.
                if ((pick <= this.RoomBias && this.roomList.Count != 0 && pick > 0) || (this.hallSegments.Count == 0 && this.roomList.Count != 0)) {
                    this.fromRoom = true;
                    bool complete = false;
                    int failCount = 0;
                    int connectorFailMax = 100;
                    int potentialX, potentialY;

                    // Keep trying to place on a room.
                    do {
                        // Pick a room at random from the list.
                        pick = this.rand.Next(this.roomList.Count);
                        RoomData room = this.roomList[pick];
                        temp.Direction = this.GenerateRandomDirection();

                        // If the room was round, set the potential points at the center of the room's walls.
                        if (room.IsRound) {
                            potentialX = room.Area.Width / 2;
                            potentialY = room.Area.Height / 2;
                        } else {
                            // Not round, so select a random point along the wall.
                            potentialX = this.rand.Next(room.Area.Width);
                            potentialY = this.rand.Next(room.Area.Height);
                        }

                        // Based on the randomly selected direction, set the connector on the right wall.
                        switch (temp.Direction) {
                            case Direction.North:
                                temp.Location.X = potentialX;
                                temp.Location.Y = -1;
                                break;
                            case Direction.South:
                                temp.Location.X = potentialX;
                                temp.Location.Y = room.Area.Height;
                                break;
                            case Direction.East:
                                temp.Location.X = room.Area.Width;
                                temp.Location.Y = potentialY;
                                break;
                            case Direction.West:
                                temp.Location.X = -1;
                                temp.Location.Y = potentialY;
                                break;
                        }

                        temp.Location.Offset(room.Area.Location);

                        // Check to make sure the point, and the points next to it are clear (wall).
                        if (this.CountNeighbors(temp.Location) == 1) {
                            // If so, complete this and continue...
                            complete = true;
                        } else {
                            // No? Increase error counter and try again, if we are at max failure, then complete and fill the
                            // return value with an empty value;
                            failCount++;
                            if (failCount == connectorFailMax) {
                                complete = true;
                                temp = new Heading(Point.Empty, Direction.Void);
                            }
                        }
                    } while (!complete);

                    // Not building a room, so pick a hallway if there is one...
                } else if (this.hallSegments.Count != 0) {
                    this.fromRoom = false;
                    bool complete = false;
                    int failCount = 0;
                    int connectorFailMax = 100;

                    // Keep trying to place on a hall segment.
                    do {
                        pick = this.rand.Next(this.hallSegments.Count);
                        Heading segment = (Heading)this.hallSegments[pick];
                        DirectionalDataFlanks segments = DirectionalDataFlanks.DirectionalRelationFlanksOnly(segment.Location, segment.Direction);
                        if (Utility.FlipCoin(this.rand)) {
                            temp.Location = segments.Right;
                            temp.Direction = segments.RightDirection;
                        } else {
                            temp.Location = segments.Left;
                            temp.Direction = segments.LeftDirection;
                        }

                        // Check to make sure the point, and the points next to it is clear (wall).
                        DirectionalDataFlanks checkFlanks = DirectionalDataFlanks.DirectionalRelationFlanksOnly(temp.Location, temp.Direction);
                        if (this.IsLocationAWall(checkFlanks.Center) && this.IsLocationAWall(checkFlanks.Left) && this.IsLocationAWall(checkFlanks.Right)) {
                            // If so, complete this and continue...
                            complete = true;
                        } else {
                            // No? Increase error counter and try again, if we are at max failure, then complete and fill the
                            // return value with an empty value;
                            failCount++;
                            if (failCount == connectorFailMax) {
                                complete = true;
                                temp = new Heading(Point.Empty, Direction.Void);
                            }
                        }
                    } while (!complete);
                } else {
                    // Since there is nothing to build to, then pick a new spot to build from.
                    //                    temp.Location = GenerateRandomPoint();
                    temp.Location = new Point(this.Level.Width >> 1, this.Level.Height >> 1);
                    temp.Direction = this.GenerateRandomDirection();
                }
            }

            return temp;
        }

        /// <summary>
        /// Calls the appropriate hall generator. Used as a universal interface for halls reguardless of hall type.
        /// </summary>
        /// <param name="connector">Location and direction to begin hall generation.</param>
        /// <param name="results">Results of weight processing. This tells the generator what type of hallway to build.</param>
        /// <returns>True if hallway was placed, false otherwise.</returns>
        private bool GenerateHall(Heading connector, WeightedResults results) {
            bool result;
            if (results.IsWander) {
                result = this.GenerateWanderingHall(connector, results);
            } else {
                result = this.GenerateStraightHall(connector, results);
            }

            if (result) {
                this.toRoom = false;
            }

            return result;
        }

        /// <summary>
        /// Generates an intelligent wandering hall. This is essentially a 'drunken sailor'
        /// algorithm with an intelligent avoidance and intersection checker.
        /// </summary>
        /// <param name="connector">Connecting point with direction to begin hall.</param>
        /// <param name="results">Results of weight processing. This tells the generator how to build the hallway.</param>
        /// <returns>True if hall was successfully placed, false if not.</returns>
        private bool GenerateWanderingHall(Heading connector, WeightedResults results) {
            Point current = new Point();
            Point previous = connector.Location;
            int length = results.HallLength;
            bool complete = false;
            bool placed = false;
            int placeCount = 0;
            int failCount = 0;
            int wanderFailMax = 10;
            Direction dir = connector.Direction;

            // Main loop for wandering algorithm....keep trying to wander until failure, max length is reached or
            // intersection occurs.
            do {
                this.intersection = new IntersectionPoint();

                // Load 'potential' location forward from the previous's location and direction.
                current = Utility.PointForward(previous, dir);

                // From the current location, get a list of all locations around it relative to
                // the direction it is facing.
                DirectionalData area = DirectionalData.DirectionalRelation(current, dir);

                // Check to see if the relative locations are within bounds, and the the current
                // location is clear.
                if (this.IsDirectionalDataWithinBounds(area) && this.IsLocationAWall(current)) {
                    // Check for intersection.
                    // Intersection is defined as:
                    //      If Forward, Forward-Left is occupied
                    //      Or if Forward, Forward-Right is occupied
                    //      Or if Forward, Forward-Left, and Forward-Right is occupied
                    //      AND Left and Right is clear
                    //      AND One or both of the rear flanks are clear.
                    // Occupied is a floor (or anything not a wall) tile and clear is a wall tile.
                    if (((!this.IsLocationAWall(area.Forward) &&
                            !this.IsLocationAWall(area.ForwardRight) &&
                            this.IsLocationAWall(area.ForwardLeft)) ||
                            (!this.IsLocationAWall(area.Forward) &&
                             !this.IsLocationAWall(area.ForwardLeft) &&
                             this.IsLocationAWall(area.ForwardRight)) ||
                            (!this.IsLocationAWall(area.Forward) &&
                             !this.IsLocationAWall(area.ForwardLeft) &&
                             !this.IsLocationAWall(area.ForwardRight))) &&
                            this.IntersectWanderingHalls) {
                        if (this.IsLocationAWall(area.Left) && this.IsLocationAWall(area.Right)) {
                            if (this.IsLocationAWall(area.BehindLeft) || this.IsLocationAWall(area.BehindRight)) {
                                placed = true;
                                complete = true;
                                this.intersection.Location = current;

                                // Find out if the intersection is caused by a room or hall...
                                foreach (RoomData roomData in this.roomList) {
                                    Rectangle room = roomData.Area;
                                    if (room.Contains(area.Forward) || room.Contains(area.ForwardLeft) || room.Contains(area.ForwardRight)) {
                                        this.intersection.ConnectedToRoom = true;
                                        break;
                                    } else {
                                        this.intersection.ConnectedToRoom = false;
                                    }
                                }
                            }
                        }
                    } else if (this.IsLocationAWall(area.Forward) &&
                               this.IsLocationAWall(area.ForwardLeft) &&
                               this.IsLocationAWall(area.ForwardRight) &&
                               this.IsLocationAWall(area.Left) &&
                               this.IsLocationAWall(area.Right) &&
                               this.IsLocationAWall(area.Center)) {
                        // If it's not an intersection then check to see if the Fronts and Flanks
                        // are cleared, if so, it's a valid connection, keep going...
                        placed = true;
                    } else {
                        // Not clear or and intersection, so increase fail counter, and try it again.
                        failCount++;
                        current = previous;
                    }
                } else {
                    // Not in bounds or current is clear, increase fail counter and try again.
                    failCount++;
                    current = previous;
                    placed = false;
                }

                // If fail counter maxes out, stop trying to place, probably a dead end....Complete and
                // leave....
                if (failCount == wanderFailMax || placeCount == length) {
                    complete = true;
                }

                // What to do if a segment was successfully placed?
                if (placed) {
                    // Well, check to see if the process is not yet complete, or complete and did not intersect a room.
                    if (!complete || (complete && !this.intersection.ConnectedToRoom)) {
                        // Place the segment
                        placeCount++;
                        this.level.GetLevel(current).TypeOfTile = TileType.Floor;
                        this.hallSegments.Add(new Heading(current, dir));
                        previous = current;
                        placed = false;
                    } else {
                        if (placeCount > 2 && !this.DoesAreaContainType(ConvertPointToRectangle(current), TileType.Door) && this.intersection.ConnectedToRoom) {
                            this.doorList.Add(current);
                        }
                    }
                }

                // If it's not complete, find a new direction that's not opposite of the way we came.
                if (!complete) {
                    Direction nextDir;
                    do {
                        nextDir = this.GenerateRandomDirection();
                    } while (nextDir == Utility.OppositeDirection(dir));
                    dir = nextDir;
                }

                if (complete && placeCount == length) {
                    // Do nothing
                }

                // Keep looping until flagged as complete.
            } while (!complete);

            // As long as something was placed, count it as complete, and return to caller.
            if (placeCount != 0) {
                return true;
            }

            // Nothing was placed, so this attempt failed. Return to caller.
            return false;
        }

        /// <summary>
        /// Generates a straight hallway given the starting point, direction, and length.
        /// </summary>
        /// <param name="connector">Heading object with connecting point and direstion.</param>
        /// <param name="results">Results of weight processing. This tells the generator how to build the hallway.</param>
        /// <returns>True if hallway was placed, otherwise false.</returns>
        private bool GenerateStraightHall(Heading connector, WeightedResults results) {
            Rectangle area = new Rectangle();
            Point endPoint = new Point();
            Direction dir = connector.Direction;
            int length = results.HallLength;
            int x = 0, y = 0;

            // Generate from the given data a rectangle to use for checking validity.
            // Set the width and height. One will be 1, as it is a hall, and the other will be the length.
            switch (dir) {
                case Direction.North:
                case Direction.South:
                    area.Width = 1;
                    area.Height = length;
                    break;
                case Direction.East:
                case Direction.West:
                    area.Width = length;
                    area.Height = 1;
                    break;
            }

            // Now, set the top, left starting point of the rectangular region that will contain the hall.
            switch (dir) {
                case Direction.North:
                    x = connector.Location.X;
                    y = connector.Location.Y - length;
                    break;
                case Direction.South:
                    x = connector.Location.X;
                    y = connector.Location.Y + 1;
                    break;
                case Direction.East:
                    y = connector.Location.Y;
                    x = connector.Location.X + 1;
                    break;
                case Direction.West:
                    y = connector.Location.Y;
                    x = connector.Location.X - length;
                    break;
            }

            area.Offset(x, y);

            // By extending the area by 1 all the way around to verify that area is clear
            if (this.IsAreaClear(ExtendArea(area))) {
                // Fill in hallway based on direction.
                switch (dir) {
                    case Direction.North:
                        endPoint.X = connector.Location.X;
                        endPoint.Y = connector.Location.Y - area.Height - 1;
                        for (y = area.Top; y < area.Bottom; y++) {
                            this.level.GetLevel(area.X, y).TypeOfTile = TileType.Floor;
                            this.hallSegments.Add(new Heading(new Point(area.X, y), dir));
                        }

                        break;
                    case Direction.South:
                        endPoint.X = connector.Location.X;
                        endPoint.Y = connector.Location.Y + area.Height + 1;
                        for (y = area.Top; y < area.Bottom; y++) {
                            this.level.GetLevel(area.X, y).TypeOfTile = TileType.Floor;
                            this.hallSegments.Add(new Heading(new Point(area.X, y), dir));
                        }

                        break;
                    case Direction.East:
                        endPoint.X = connector.Location.X + area.Width + 1;
                        endPoint.Y = connector.Location.Y;
                        for (x = area.Left; x < area.Right; x++) {
                            this.level.GetLevel(x, area.Y).TypeOfTile = TileType.Floor;
                            this.hallSegments.Add(new Heading(new Point(x, area.Y), dir));
                        }

                        break;
                    case Direction.West:
                        endPoint.X = connector.Location.X - area.Width - 1;
                        endPoint.Y = connector.Location.Y;
                        for (x = area.Left; x < area.Right; x++) {
                            this.level.GetLevel(x, area.Y).TypeOfTile = TileType.Floor;
                            this.hallSegments.Add(new Heading(new Point(x, area.Y), dir));
                        }

                        break;
                }

                // Add the point just past the last segment to the preferred connectors list.
                this.preferredConnectors.Add(new Heading(endPoint, dir));

                // If hallway was placed, then return true to caller.
                return true;
            }

            // Hallway was not placed, so return false to caller.
            return false;
        }

        /// <summary>
        /// Generates a room attached to the specified connector point, in the specified direction, with the specified size.
        /// This can generate either a standard rectangular room, or a round room.
        /// </summary>
        /// <param name="connector">Heading to begin building a room from.</param>
        /// <param name="results">Results of weight processing. This tells the generator what type of room to build.</param>
        /// <returns>True if room was placed, false otherwise.</returns>
        private bool GenerateRoom(Heading connector, WeightedResults results) {
            Direction dir = connector.Direction;
            Rectangle area = new Rectangle();
            area.Location = Point.Empty;
            area.Size = results.RoomCaveSize;
            bool round = results.IsRound;
            int potentialX, potentialY;
            int x = 0, y = 0;
            int radius = 0;
            Point center = new Point();

            // If room is to be round, adjust room size to a square with an odd size for a center point.
            if (round) {
                // Find which side is larger, width or height, use the larger of the two.
                int max = Math.Max(area.Width, area.Height);

                // If selected size is even....
                if (max % 2 == 0) {
                    // And it's not equal to the max size allowed...
                    if (max != this.maxRoomSize) {
                        // .... Then add one to make it odd.
                        max += 1;
                    } else {
                        // Or, remove one to make it odd.
                        max -= 1;
                    }
                }

                // Now, make width and height equal the new figure.
                area.Width = area.Height = max;

                // Make the potential starting point for the room in the center as this is the only point that touches the
                // edges.
                potentialX = connector.Location.X - (area.Width / 2);
                potentialY = connector.Location.Y - (area.Height / 2);
            } else {
                // Not round, so select a random point that will connect the room to the connector point.
                potentialX = this.rand.Next(connector.Location.X - area.Width + 1, connector.Location.X + 1);
                potentialY = this.rand.Next(connector.Location.Y - area.Height + 1, connector.Location.Y + 1);
            }

            // Now that we have the potential starting points, finish building the rectangle(square).
            switch (dir) {
                case Direction.North:
                    x = potentialX;
                    y = connector.Location.Y - area.Height;
                    break;
                case Direction.South:
                    x = potentialX;
                    y = connector.Location.Y + 1;
                    break;
                case Direction.East:
                    y = potentialY;
                    x = connector.Location.X + 1;
                    break;
                case Direction.West:
                    y = potentialY;
                    x = connector.Location.X - area.Width;
                    break;
            }

            area.Offset(x, y);

            // If room is round, set the center point...
            if (round) {
                center.X = area.Left + (area.Width / 2);
                center.Y = area.Top + (area.Height / 2);
                radius = area.Width / 2;
            }

            // Check to see if area is cleared. Expand the area by 1 all the way around to ensure no overlaps.
            if (this.IsAreaClear(ExtendArea(area))) {
                // Clear, so, place the room...
                for (y = area.Top; y < area.Bottom; y++) {
                    for (x = area.Left; x < area.Right; x++) {
                        // If the room is round and the distance from the current point to the center is less than or equal to the
                        // radius, or if the room is not round, then place the point.
                        if ((round && Utility.Distance(center, new Point(x, y)) <= radius) || !round) {
                            this.level.GetLevel(x, y).TypeOfTile = TileType.Floor;
                        }
                    }
                }

                this.toRoom = true;

                // Add new room with the dimensions to the room list.
                this.roomList.Add(new RoomData(area, round, results.Type));

                // Since the room was placed, return true to the caller.
                return true;
            }

            // The room wasn't placed, so return false to the caller.
            return false;
        }

        /// <summary>
        /// Initializes the variables and the level for a fresh generation.
        /// </summary>
        private void Initialize() {
            // Set random seed. If one was given, then use that, if not, use default.
            if (this.seed != 0) {
                this.rand = new Random(this.seed);
            } else {
                this.rand = new Random();
            }

            if (this.Level == null) {
                this.SetSize(new Size(64, 64));
            }

            double densPercent = (double)this.Density / 1000;
            this.failure = (int)((double)(this.Level.Width * this.Level.Height) * densPercent);

            // Fill level with default tiles which is a wall tile.
            for (int y = this.level.Bounds.Top; y < this.level.Bounds.Bottom; y++) {
                for (int x = this.level.Bounds.Left; x < this.level.Bounds.Right; x++) {
                    this.level.SetLevel(x, y, new TileData());
                    this.level.GetLevel(x, y).TypeOfTile = TileType.Wall;
                }
            }
        }

        /// <summary>
        /// Generates a random direction leaving out the VOID.
        /// </summary>
        /// <returns>A random direction.</returns>
        private Direction GenerateRandomDirection() {
            return (Direction)this.rand.Next(1, 5);
        }

        /*/// <summary>
        /// Generates a random point within the bounds of the level.
        /// </summary>
        /// <returns>A random point.</returns>
        private Point GenerateRandomPoint() {
            Point temp = new Point();
            do {
                temp.X = rand.Next(0, this.Width);
                temp.Y = rand.Next(0, this.Height);
            } while (!IsLocationWithinBounds(temp));
            return temp;
        }*/

        /// <summary>
        /// Checks to see if the location has a wall or not.
        /// </summary>
        /// <param name="location">Location to check.</param>
        /// <returns>True if there is a wall at the point, otherwise false.</returns>
        private bool IsLocationAWall(Point location) {
            if (this.Level.IsLocationWithinBounds(location)) {
                if (this.level.GetLevel(location.X, location.Y).TypeOfTile != TileType.Wall) {
                    return false;
                } else {
                    return true;
                }
            } else {
                return false;
            }
        }

        /// <summary>
        /// Checks to see if the area is clear(wall) or not.
        /// </summary>
        /// <param name="area">Area to check.</param>
        /// <returns>True if area only contains walls, false otherwise.</returns>
        private bool IsAreaClear(Rectangle area) {
            if (this.IsAreaWithinBounds(area)) {
                for (int y = area.Top; y < area.Bottom; y++) {
                    for (int x = area.Left; x < area.Right; x++) {
                        if (this.level.GetLevel(x, y).TypeOfTile != TileType.Wall) {
                            return false;
                        }
                    }
                }

                return true;
            } else {
                return false;
            }
        }

        /// <summary>
        /// Checks to see if area is within the level boundries.
        /// </summary>
        /// <param name="area">Area to check.</param>
        /// <returns>True if the entire area is with bounds, false otherwise.</returns>
        private bool IsAreaWithinBounds(Rectangle area) {
            return this.Level.Bounds.Contains(area);
        }

        /// <summary>
        /// Check to see if a grid of nine points are clear.
        /// </summary>
        /// <param name="data">Grid of points from the Utility.DirectionalData object.</param>
        /// <returns>True if all of the points are clear, false otherwise.</returns>
        private bool IsDirectionalDataWithinBounds(DirectionalData data) {
            bool temp = false;
            foreach (Point point in data.ToArray()) {
                if (this.Level.Bounds.Contains(point)) {
                    temp = true;
                } else {
                    temp = false;
                    break;
                }
            }

            return temp;
        }

        /// <summary>
        /// Check to ensure the total weight values equal 100. Will throw an exception if it does not.
        /// </summary>
        private void CheckWeights() {
            int results = (int)Math.Round(this.TotalWeights());
            if (results != 100) {
                throw new UnbalancedWeightsException("Weight values add together do not total 100. (" +
                                                     results.ToString(System.Globalization.CultureInfo.CurrentCulture) + ")");
            }
        }

        /// <summary>
        /// Processes the structure weights and returns the results. This will check the weights first to ensure validity.
        /// </summary>
        /// <returns>The results of the processing.</returns>
        private WeightedResults ProcessStructureWeights() {
            this.CheckWeights();
            WeightedResults temp = new WeightedResults();
            this.minRoomSize = 5;
            this.maxRoomSize = 9;
            temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
            temp.HallLength = 0;
            temp.IsCave = false;
            temp.IsRoom = false;
            temp.IsRound = false;
            temp.IsWander = false;
            double tally = 0;
            int pick = this.rand.Next(1, 101);
            string type = string.Empty;

            foreach (KeyValuePair<StructureType, double> kvp in this.StructureWeightsList) {
                tally += kvp.Value;
                if (pick <= tally) {
                    type = kvp.Key.ToString();
                    temp.Type = kvp.Key;
                    break;
                }
            }

            if (type.Contains("Small")) {
                this.minRoomSize = 4;
                this.maxRoomSize = 7;
                temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
            } else if (type.Contains("Large")) {
                this.minRoomSize = 7;
                this.maxRoomSize = 11;
                temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
            } else if (type.Contains("Huge")) {
                this.minRoomSize = 9;
                this.maxRoomSize = 15;
                temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
            }

            if (type.Contains("Circular")) {
                temp.IsRound = true;
            }

            if (type.Contains("Room")) {
                temp.IsRoom = true;
                if (type.Contains("Long")) {
                    this.minRoomSize = 4;
                    this.maxRoomSize = 15;
                    bool picker = Utility.FlipCoin(this.rand);
                    if (picker) {
                        temp.RoomCaveSize = new Size(this.rand.Next(4, 7), this.rand.Next(7, 15));
                    } else {
                        temp.RoomCaveSize = new Size(this.rand.Next(7, 15), this.rand.Next(4, 7));
                    }
                }
            } else {
                if (type.Contains("Long")) {
                    temp.HallLength = this.rand.Next(7, 15);
                } else {
                    temp.HallLength = this.rand.Next(1, 9);
                }

                if (type.Contains("Wander")) {
                    temp.HallLength = this.rand.Next(10, 20);
                }
            }

            if (type.Contains("Cave")) {
                temp.IsCave = true;
                temp.IsRoom = true;
                if (type.Contains("Long")) {
                    this.minRoomSize = 4;
                    this.maxRoomSize = 16;
                    bool picker = Utility.FlipCoin(this.rand);
                    if (picker) {
                        temp.RoomCaveSize = new Size(this.rand.Next(4, 7), this.rand.Next(7, 15));
                    } else {
                        temp.RoomCaveSize = new Size(this.rand.Next(7, 15), this.rand.Next(4, 7));
                    }
                } else if (type.Contains("Large")) {
                    this.minRoomSize = 8;
                    this.maxRoomSize = 16;
                    temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
                } else {
                    this.minRoomSize = 4;
                    this.maxRoomSize = 8;
                    temp.RoomCaveSize = new Size(this.rand.Next(this.minRoomSize, this.maxRoomSize), this.rand.Next(this.minRoomSize, this.maxRoomSize));
                }
            }

            if (type.Contains("Wander")) {
                temp.IsWander = true;
            }

            return temp;
        }

        /// <summary>
        /// Checks to see if a type of tile is within an area.
        /// </summary>
        /// <param name="area">Area to check.</param>
        /// <param name="type">Type to check for.</param>
        /// <returns>True if area contains type, false otherwise.</returns>
        private bool DoesAreaContainType(Rectangle area, TileType type) {
            if (this.IsAreaWithinBounds(area)) {
                for (int y = area.Top; y < area.Bottom; y++) {
                    for (int x = area.Left; x < area.Right; x++) {
                        if (type == TileType.Door && this.doorList.Contains(new Point(x, y))) {
                            return true;
                        } else if (this.level.GetLevel(x, y).TypeOfTile == type) {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Counts the number of occupied points to the top, bottom, left, and right of the given location. This does
        /// not check the given location.
        /// </summary>
        /// <param name="location">Location whose neighbors are to be checked.</param>
        /// <returns>Number of occupied neighbors. Number will be from 0 - 4.</returns>
        private int CountNeighbors(Point location) {
            int result = 0;
            Point top = location;
            top.Offset(0, -1);
            Point right = location;
            right.Offset(1, 0);
            Point left = location;
            left.Offset(-1, 0);
            Point bottom = location;
            bottom.Offset(0, 1);
            if (this.Level.IsLocationWithinBounds(top)) {
                if (this.level.GetLevel(top.X, top.Y).TypeOfTile != TileType.Wall || this.doorList.Contains(top)) {
                    result++;
                }
            }

            if (this.Level.IsLocationWithinBounds(bottom)) {
                if (this.level.GetLevel(bottom.X, bottom.Y).TypeOfTile != TileType.Wall || this.doorList.Contains(bottom)) {
                    result++;
                }
            }

            if (this.Level.IsLocationWithinBounds(left)) {
                if (this.level.GetLevel(left.X, left.Y).TypeOfTile != TileType.Wall || this.doorList.Contains(left)) {
                    result++;
                }
            }

            if (this.Level.IsLocationWithinBounds(right)) {
                if (this.level.GetLevel(right.X, right.Y).TypeOfTile != TileType.Wall || this.doorList.Contains(right)) {
                    result++;
                }
            }

            return result;
        }
    }
}